#!/bin/sh
# Copyright 2015 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# /init script for use in the loader kernel.

. /lib/init.sh

LOG_DIR=/log
LOG_FILE=${LOG_DIR}/init.log

KERNA="2"
KERNB="4"

KERNEL_KEY="/kernel_subkey.vbpubk"
RECOVERY_KEY="/recovery_key.vbpubk"

# Extract and export kernel arguments
export_args() {
  # We trust our kernel command line explicitly.
  local arg=
  local key=
  local val=
  local acceptable_set='[:alnum:]_'
  for arg in "$@"; do
    key=$(echo "${arg%%=*}" | \
            tr '[:lower:]' '[:upper:]' | \
            tr -dc "${acceptable_set}" '_')
    val="${arg#*=}"
    export "KERN_ARG_${key}"="${val}"
    echo "Exporting kernel argument ${key} as KERN_ARG_${key}"
  done
}

# See if the kernel has a valid signature for the specified key.
verify_kernel() {
  local kern="$1"
  local key

  for key in "${KERNEL_KEY}" "${RECOVERY_KEY}"; do
    futility verify -k "${key}" "${kern}" >/dev/null && break
  done
}

# Extract the kernel command line and replace the % markers.
process_cmdline() {
  local disk="$1"
  local kern="$2"
  local part="$3"
  local guid

  guid=$(cgpt show "${disk}" -i ${part} -u)
  # TODO: Support all the replacement chars that depthcharge does.  See:
  # depthcharge/src/boot/commandline.c.
  CMDLINE=$(futility dump_kernel_config "${kern}" | \
              sed "s/%U/${guid}/g")
}

# Locate the kernel that has the highest priority and is valid.
# TODO: This isn't entirely correct.  See this document for details:
# http://www.chromium.org/chromium-os/chromiumos-design-docs/disk-format#TOC-Selecting-the-kernel
process_disk() {
  local disk="$1"
  local prioa priob k order

  # If the disk is USB/removable, wait for it to show up.
  local i=0
  while [ ${i} -lt 20 ]; do
    if [ ! -e "${disk}" ]; then
      echo ". "
      sleep 1
    else
      break
    fi
  done

  prioa=$(cgpt show "${disk}" -i ${KERNA} -P)
  priob=$(cgpt show "${disk}" -i ${KERNB} -P)
  if [ ${prioa} -ge ${priob} ]; then
    order="${KERNA} ${KERNB}"
  else
    order="${KERNB} ${KERNA}"
  fi

  for k in ${order}; do
    # Support /dev/sda# style disks.
    KERNEL="${disk}${k}"
    if [ ! -e "${KERNEL}" ]; then
      # Support /dev/mmcblk0p# style disks.
      KERNEL="${disk}p${k}"
      if [ ! -e "${KERNEL}" ]; then
        continue
      fi
    fi
    if verify_kernel "${KERNEL}"; then
      process_cmdline "${disk}" "${KERNEL}" "${k}"
      return 0
    fi
  done
  return 1
}

main() {
  set -x
  exec >"${LOG_FILE}" 2>&1

  initialize

  # Export the kernel command line as a parsed blob prepending KERN_ARG_ to each
  # argument.
  export_args $(sed -e 's/"[^"]*"/DROPPED/g' /proc/cmdline)

  # Find the highest priority kernel and its root.
  if ! process_disk "${KERN_ARG_LOADER_ROOT}"; then
    echo "could not find a verified kernel"
    exit 1
  fi

  # Extract the kernel.
  futility vbutil_kernel \
    --get-vmlinuz "${KERNEL}" \
    --vmlinuz-out /dev/stdout |

  # Load it up.
  kexec \
    --command-line="${CMDLINE}" \
    -l /dev/stdin

  # Run it!
  kexec -e

  # For debugging.
  #env; setsid cttyhack sh; poweroff -f
}

# Make this source-able for testing.
if [ "$0" = "/init" ]; then
  main "$@"
  # Should never reach here.
  exit 1
fi
